#include<functional>
#include<cmath>
#include<thread>
#include<utility>

#define LCCTOOLBOX_DLL_IMPORT_LCCTOOLBOX
#include<lcctoolbox_block.hpp>
#ifdef _WIN32
#pragma comment(lib,"lcctoolbox")
#endif


namespace LccStuffCollector{
namespace Bezier{



// the T_element is required to have member function operator*(float) operator+(T_element)

template<class T_element>
struct commonPart{
	std::function<size_t(void)> size;// get the points' count
	std::function<T_element&(size_t)> element;
	bool working() const {return size&&element;}
};

template<class T_point>
struct InputWrapper:public commonPart<T_point>{
	std::function<bool(size_t)> is_onCurve;// detect if the date to correspond index is on curve
	bool working() const {return commonPart<T_point>::working()&&is_onCurve;}
};

template<class T_point>
struct OutputWrapper:public commonPart<T_point>{
	float proportion=1.0f/100;// compare to full glyph size
	std::function<void(void)> clear;
	std::function<void(size_t)> grow;
	std::function<void(size_t)> shrink;
	bool working() const {return commonPart<T_point>::working()&&clear&&grow&&shrink;}
};


// loop process input
//  detected_func => output base on density
//  if(moreItem)
//   for(moreRule_func => moreOutput base on moreArg)
template<class T_point>
size_t process( const InputWrapper<T_point> input, const OutputWrapper<T_point> output){
	if(!input.working()||!output.working()) return 0;
	// < prepare >
	// firstly we get the start, control and end points
	// then gen bezier
	if(!input.size()) return 0;

	// [ find the start point ]
	size_t input_index=0;
	while(!input.is_onCurve(input_index)) ++input_index;

	// [ predefine single frag bezier gen function | this piece would like to be performed in GPU | but now use threads cause by limited knowledge ]
	auto pieceBezier=[&]( const T_point p1, const T_point& c, const T_point p2, LccToolbox::block<T_point>& subOutput, size_t frags)
	// here we not use left ref cause during executing the slot's data might change
	{	// frags should at least be 1
		if(frags<1) frags=1;
		// ((1-t)^2)p1+2t(1-t)c+(t^2)p2
		size_t i=frags-1;
		for(;i;--i)
		{	float t=float(i)/frags;
			auto part1=p1;
			auto part2=c;
			auto part3=p2;
			part1*=pow(1-t,2);
			part2*=(2*t*(1-t));
			part3*=pow(t,2);
			// subOutput[i]=p2Mid*pow(-t+1,2)+cMid*2*t*(-t+1)+p1Mid*pow(t,2);
			subOutput[i]=part1+part2+part3;
		}
		subOutput[i]=p2;
	};

	// < process or gen also >
	LccToolbox::block<std::thread> jobUnit;
	LccToolbox::block<std::pair<LccToolbox::block<T_point>*,size_t>> outputPiece;
	const size_t range_max=input.size()-1;// index to last element of input
	output.clear();
	size_t output_size=0;
	while(input_index<range_max)
	{	// [ insert the frag statrt point into output ]
		if(output_size+1>output.size()) output.grow((output_size+1-output.size())*10);
		output.element(output_size++)=input.element(input_index);

		// pointSlot is use to record between 2 isn't on curve's point, the positive is use to reduce data move in pointSlot
		T_point pointSlot[2];
		pointSlot[0]=input.element(input_index);
		bool positive=true;
		// * case straight line
		if(input.is_onCurve(++input_index)) continue;
		// * if is bezier
		else for(;;)
		{	// [ prepare the space ]
			// { get frag count }
			size_t frag_count=4;// temp
			// alloc sub block for sub thread & alloc output
			jobUnit.grow(1);
			outputPiece.grow(1);
			auto temp=new LccToolbox::block<T_point>{frag_count};
			outputPiece[outputPiece.size()-1].first=temp;
			outputPiece[outputPiece.size()-1].second=output_size;
			// update output index
			output_size+=frag_count;

			// [ get data & process ]
			// check if out range
			if(++input_index>range_max) break;
			// ready process data
			bool is_endpoint=input.is_onCurve(input_index);
			if(is_endpoint)
				pointSlot[positive?1:0]=input.element(input_index);
			else
				pointSlot[positive?1:0]=(input.element(input_index-1)+input.element(input_index))/2;
			// depatch thread
			jobUnit[jobUnit.size()-1]=std::thread{pieceBezier,pointSlot[positive?0:1],input.element(input_index-1),pointSlot[positive?1:0],*temp,frag_count};

			if(is_endpoint) {++input_index;break;}// if is_endpoint, then the point will be add in output by sub thread, so here op is for avoiding re add
		}
	}
	/* out have these situation:
	1. out range during gen bezier
	2. loop finish:
	 2-1. last element is recorded cause the last element is the endpoint of a bezier
	 2-2. the 2nd back element is on curve
	* - just case 2-2 & last element is on curve then the last element should be take
	*/

	// < tidy & conclude >
	// [ last element determine ]
	if(input.is_onCurve(range_max-1)&&input.is_onCurve(range_max))
		output.element(output_size)=input.element(range_max);
	// [ output resize ]
	size_t sizeRecord_o=output.size();
	if(sizeRecord_o>output_size)
		output.shrink(sizeRecord_o-output_size);
	else if(sizeRecord_o<output_size)
		output.grow(output_size-sizeRecord_o);
	// [ job join & outputPiece copy & clear ]
	// join threads
	for(size_t i=jobUnit.size()-1;i!=size_t(-1);--i) jobUnit[i].join();
	// copy outputPieces to output and free outputPieces
	auto copyUnit=[&outputPiece,&output](size_t i)
	{	size_t frag_count=outputPiece[i].first->size();
		size_t startpos=outputPiece[i].second;
		for(size_t j=0;j<frag_count;++j)
			output.element(startpos+j)=(*(outputPiece[i].first))[j];
		delete outputPiece[i].first;
	};
	for(size_t i=outputPiece.size()-1;i!=size_t(-1);--i)
		jobUnit[i]=std::thread{copyUnit,i};
	for(size_t i=jobUnit.size()-1;i!=size_t(-1);--i) jobUnit[i].join();

	return output_size;
}




}// namespace Bezier
}// namespace LccStuffCollector